package com.tanhua.sso.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.common.mapper.*;
import com.tanhua.common.pojo.*;
import com.tanhua.dubbo.server.api.HuanXinApi;
import com.tanhua.dubbo.server.api.LogApi;
import com.tanhua.dubbo.server.pojo.Log;
import com.tanhua.dubbo.server.api.UserLocationApi;
import com.tanhua.sms.SmsTemplate;
import com.tanhua.common.exception.BusinessException;
import com.tanhua.common.vo.ErrorResult;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.bson.types.ObjectId;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class UserService {

    @Reference(version = "1.0.0")
    private HuanXinApi huanXinApi;

    //注入RedisTemplate来操作redis
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //注入mapper
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private SettingsMapper settingsMapper;

    //注入SmsTemplate
    @Autowired
    private SmsTemplate smsTemplate;

    @Reference
    private LogApi logApi;

    @Value("${jwt.secret}")
    private String secret;
    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private UserFreezeMapper userFreezeMapper;

    @Reference
    private UserLocationApi userLocationApi;

    private final static String REDIS_FREEZE_USER = "REDIS_FREEZE_USER";
    private final static String REDIS_FREEZE_USER_ID = "REDIS_FREEZE_USER_ID_";

    /**
     * 初始化方法,在用户进行登录之前,将被冻结的用户id存入redis中
     */
    @PostConstruct
    public void init() {
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        List<UserInfo> userInfoList = userInfoMapper.selectList(wrapper);
        for (UserInfo userInfo : userInfoList) {
            if (userInfo.getUserStatus() == 2){
                LambdaQueryWrapper<UserFreeze> wrapper1 = new LambdaQueryWrapper<>();
                wrapper1.eq(UserFreeze::getUserId,userInfo.getUserId());
                UserFreeze userFreeze = userFreezeMapper.selectOne(wrapper1);
                redisTemplate.opsForHash().put(REDIS_FREEZE_USER, REDIS_FREEZE_USER_ID + userInfo.getUserId(), Convert.toStr(userFreeze.getExpireDate().getTime()));
            }
        }
    }

    //发送手机号
    public void sendMobilePhone(String phone) {
        /*
            冻结判断:
                判断用户是否被冻结登录
         */
        //根据手机号获取user对象
        LambdaQueryWrapper<User> warpper = new LambdaQueryWrapper<>();
        warpper.eq(User::getMobile, phone);
        User user = userMapper.selectOne(warpper);
        //判断该用户是否存在
        if (ObjectUtil.isNotEmpty(user)) {
            //存在,判断该用户是否是被冻结的用户
            if (redisTemplate.opsForHash().hasKey(REDIS_FREEZE_USER, REDIS_FREEZE_USER_ID + user.getId())){
                //判断该用户是否是登录被冻结的用户
                LambdaQueryWrapper<UserFreeze> wrapper = new LambdaQueryWrapper<>();
                wrapper.eq(UserFreeze::getUserId, user.getId());
                UserFreeze userFreeze = userFreezeMapper.selectOne(wrapper);
                if (userFreeze.getFreezingRange() == 1) {
                    //该用户是登录被冻结的用户
                    //查询该用户的冻结时间是否已过期限
                    //冻结期限的时间戳
                    long expireDate = userFreeze.getExpireDate().getTime();
                    //获取当前系统时间的毫秒值
                    long nowMillisecond = System.currentTimeMillis();
                    //判断该用户的冻结时间是否已过期限
                    if (nowMillisecond >= expireDate) {
                        //该用户的冻结时间已过期限
                        userFreeze.setReasonsForThawing("冻结时间期限已过");
                        //放行用户接收短信验证码进行登录,并将tb_user_info表中用户状态修改为:1(正常)并进行保存
                        UserInfo userInfo = new UserInfo();
                        userInfo.setUserStatus(1);
                        /*UpdateWrapper<UserInfo> wrapper1 = new UpdateWrapper<>();
                        wrapper1.eq("user_id", user.getId());
                        userInfoMapper.update(userInfo,wrapper1);*/
                        LambdaUpdateWrapper<UserInfo> wrapper1 = new LambdaUpdateWrapper<>();
                        wrapper1.eq(UserInfo::getUserId, user.getId());
                        userInfoMapper.update(userInfo,wrapper1);
                    } else {
                        //该用户的冻结时间未过期限
                        //禁止登录,并提示用户:您的账号已被冻结
                        String reasonsForFreezing = userFreeze.getReasonsForFreezing();
                        throw new BusinessException(ErrorResult.loginFreeze(reasonsForFreezing));
                    }
                }
            }
        }

        //生成redisKey
        String redisKey = "CHECK_CODE_" + phone;
        //判断redis中当前Key是否存在
        if (redisTemplate.hasKey(redisKey)) {
            //存在 提示
            throw new BusinessException(ErrorResult.redisKeyError());
        }
        //生成验证码
        String code = "123456";

        //保存验证码到redis 指定时间为5分钟
        redisTemplate.opsForValue().set(redisKey, code, Duration.ofMinutes(5));
    }

    //验证手机号和验证码
    public Map<String, Object> checkMobileAndCode(String phone, String code) {
        //标记位
        boolean isNew = false;
        String key = "CHECK_CODE_" + phone;
        //1.获取redis中的验证码
        String redisCode = redisTemplate.opsForValue().get(key);

        //2.与输入验证码对比
        if (!code.equals(redisCode)) {
            //2.1 失败 抛出异常
            throw new RuntimeException("验证码输入有误!");
        }

        //2.2 验证成功
        //3. 查询数据库验证当前手机号是否是新用户
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getMobile, phone);
        User user = userMapper.selectOne(wrapper);
        //4. 判断查询的user是否为空
        if (user == null) {

            //是新用户 注册新用户
            user = new User();
            user.setMobile(phone);
            user.setPassword(DigestUtils.md5Hex("123456"));
            userMapper.insert(user);

            //配置用户默认设置
            //1.陌生人问题
            Question question = new Question();
            question.setUserId(user.getId());
            question.setTxt("你喜欢什么运动?");
            questionMapper.insert(question);
            //2.通用设置
            Settings settings = new Settings();
            settings.setUserId(user.getId());
            settings.setGonggaoNotification(1);
            settings.setPinglunNotification(1);
            settings.setLikeNotification(1);
            settingsMapper.insert(settings);

            //修改标记位
            isNew = true;

            //注册环信用户
            Boolean result = this.huanXinApi.register(user.getId());
            if (!result) {
                //注册环信失败，记录日志
                log.error("注册环信用户失败~ userId = " + user.getId());
            }
        }
        //清除已使用的redisKey
        redisTemplate.delete(key);

        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("mobile", user.getMobile());//手机号
        claims.put("id", user.getId());//用户id

        //token生成方式
        String token = Jwts.builder()
                //.setHeader(header)//可省略
                //存放数据的位置
                .setClaims(claims)
                //设置加密方法和加密盐
                .signWith(SignatureAlgorithm.HS256, secret)
                //设置过期时间
                .setExpiration(new DateTime().plusHours(12).toDate())
                .compact();

        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("isNew", isNew);
        System.out.println(token);
        //保存用户登录日志
        saveLog(user.getId(),"01");
        return map;
    }

    //解析token
    public User queryToken(String token) {
        try {
            // 通过token解析数据
            Map<String, Object> body = Jwts.parser()
                    .setSigningKey(secret)//盐值要相同
                    .parseClaimsJws(token)
                    .getBody();
            //将json格式的body数据装换成user对象
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(body);
            User user = JSON.parseObject(json, User.class);
            Log log = new Log();
            saveLog(user.getId(),"02");
            return user;
        } catch (Exception e) {
            throw new BusinessException(ErrorResult.tokenFail());
        }
    }

    //保存用户登录日志
    private void saveLog(Long userId,String type) {
        //封装数据保存
        Log log = new Log();
        log.setId(new ObjectId());
        log.setUserId(userId);
        log.setLogTime(System.currentTimeMillis());
        log.setEquipment("Android");
        log.setLogType(type);
        String place = logApi.queryUserLocation(userId);
        if (StrUtil.isNotEmpty(place)){
            log.setPlace(place);
        }

        //调用dubbo服务保存数据
        logApi.saveLog(log);
    }
}
